import axios from 'axios'
import React, { useEffect, useRef, useState, createContext, useContext } from 'react'

// useContext
const Context = createContext()
// 如果是跨文件的组件，需要写成：
// export const Context = createContext()
// 组件A 套着 组件C
function CompA() {
  return (
    <div>
      <div>这是组件A，A中套着C</div>
      <CompC />
    </div>
  )
}

function CompC() {
  // 使用useContext
  // 下面这个值名字可以不和父组件的值名字相同
  const contextValue = useContext(Context)
  return (
    <div>
      <div>这是组件C，App传过来的值：{contextValue}</div>
    </div>
  )
}

// 主函数
function App() {
  // useEffect()
  const [num, setNum] = useState(0)
  // 每次更新渲染执行：
  // useEffect(() => {
  //   console.log(num, '无依赖项，每次num更新执行')
  // })
  // 空数组依赖项：
  // useEffect(() => {
  //   console.log(num, '空数组依赖项，只执行一次')
  // }, [])
  // 特定依赖项
  useEffect(() => {
    console.log(num, '特定依赖项，改变时执行')
  }, [num])

  // 清理副作用：
  const [count, setCount] = useState(0)
  useEffect(() => {
    const Timer = window.setInterval(() => {
      setCount(count + 1)
    }, 1000)
    if (count === 6) {
      setCount(0)
    }
    return () => window.clearInterval(Timer)
  }, [count])

  // 使用 useEffect 发起网络请求
  useEffect(() => {
    async function getData() {
      const res = await axios.get('http://geek.itheima.net/v1_0/channels')
      console.log('111', res)
      const res2 = await axios.get('http://geek.itheima.net/v1_0/channels')
      console.log('222', res2)
      axios.get('http://geek.itheima.net/v1_0/channels').then((res) => {
        console.log('333', res)
      })
    }
    getData()

    // getData1()
    // getData2()
  }, [])
  // const getData1 = async () => {
  //   const res = await axios.get('http://geek.itheima.net/v1_0/channels')
  //   console.log('111', res)
  // }
  // const getData2 = async () => {
  //   const res = await axios.get('http://geek.itheima.net/v1_0/channels')
  //   console.log('222', res)
  // }

  // 自定义一个hook函数，实现获取滚动距离y
  const useWindowScroll = () => {
    const [y, setY] = useState(0)
    window.addEventListener('scroll', () => {
      const h = document.documentElement.scrollTop
      setY(h)
    })
    return [y]
  }
  const [y] = useWindowScroll()

  // 自定义一个hook函数，实现将数据自动同步到localStorage
  const useLocalStorage = (defaultMsg) => {
    const [msg, setMsg] = useState(defaultMsg)
    // 每次只要修改 msg ，就会自动同步到本地
    useEffect(() => {
      window.localStorage.setItem('data', msg)
    }, [msg])
    return [msg, setMsg]
  }
  const [msg, setMsg] = useLocalStorage(1)

  // useRef
  const testRef = useRef(null)
  useEffect(() => {
    console.log(testRef.current)
    testRef.current.style.color = 'blue'
  }, [testRef])

  // useContext
  const [contextValue, setContextValue] = useState(100)

  return (
    <Context.Provider value={contextValue}>
      <div>
        {/* hooks */}
        {/* 
          useStates() 状态的读取和修改
            语法：const [num, setNum] = useState(0)
            1、useState 传过来的参数，作为 num 的初始值
            2、[num, setNum] 这里的写法是一个解构赋值（注意：顺序不可换）
            3、setNum 函数，作用是用来修改 num ，依旧保持不能直接修改原值还是生成一个新值替换原值
            4、两个是绑在一起的，后面的 setNum 只能用来修改 num

            组件的更新：
            当调用 setNum 的时候，更新过程：
            首次渲染：
              首次被渲染的时候，组件内部的代码会被执行一次
              其中 useState 也会跟着执行，注意：初始值只有在首次渲染的时候生效
            更新渲染：
              app 组件会再次渲染，这个 setNum 函数会再次执行
              useState 再次执行，得到的新的 num 值，并且用新值进行渲染
            
            回调函数的参数
            使用场景：
            参数只会在组件的初始渲染中起作用，后续渲染时会被忽略。如果初始 state 需要通过计算才能获得，
            则可以传入一个函数，在函数中计算并返回初始的 state，此函数只在初始渲染是被调用
            语法：
            const [num, setNum] = useState(() => { 计算逻辑, return })
        */}
        {/* 
          useEffect() 副作用函数
            一个函数除了主作用，其余都是副作用，对于react组件来说，主作用就是根据数据渲染UI，除此之外都是副作用
            常见的副作用有：发起请求、手动修改Dom、localStorage操作
            使用：
              1、导入 useEffect() 函数
              2、在函数组件中执行，传入回调，并且定义副作用
              3、当我们通过修改状态更新组件时，副作用也会不断执行
            通过依赖项控制执行时机
              1、默认状态（无依赖项）：初始化先执行一次，等到每次数据修改组件更新时再次执行
              2、添加空数组依赖项：只会在首次渲染的时候执行一次
              3、添加一个特定依赖项：在函数首次渲染时执行，在依赖项发生变化时重新执行
            注意：useEffect 回调函数中遇到的数据就是依赖数据，就应该出现在依赖项数组中，如果不添加依赖项就会有bug出现

            清理副作用：
            使用场景：
            在组件被销毁时，如果有些副作用操作需要被清理，就可以使用此语法，比如常见的定时器
            语法：
            useEffect(() => {
              console.log('副作用执行')
              // 副作用函数执行时机为：在下一次副作用函数执行之前执行
              return () => {
                console.log('清理副作用执行')
                // 在这里写清理副作用的代码
              }
            })

            使用 useEffect 发起网络请求
            注意：不应该在 useEffect 的回调函数前面加 async
            正确写法：
            useEffect(() => {
              async function 函数名() {
                请求加 await
              }
              函数名()
            })
        */}
        <h3>useEffect</h3>
        <div>普通 useEffect 使用：</div>
        <div>num值：{num}</div>
        <button onClick={() => setNum(num + 1)}>点击num加一</button>
        <br />
        <br />
        <div>清理副作用：{count}</div>
        <br />
        <div>使用useEffect发起网络请求：见打印台</div>

        {/* 
          自定义 Hook
          需求1：自定义一个hook函数，实现获取滚动距离y
          需求2：自定义一个hook函数，实现将数据自动同步到localStorage
        */}
        <h3>自定义hook</h3>
        <div>自定义hook得到滚动距离：{y}</div>
        <div>
          自定义hook，保存localStorage：{msg}
          <button onClick={() => setMsg(Math.random().toFixed(2))}>点击改变随机数并储存</button>
        </div>

        {/* 
          useRef()
          使用步骤：
          1、导入 useRef()
          2、执行 useRef 函数并传入 null，返回值为一个对象，内部有一个 current 属性存放拿到的 dom 对象（组件实例）
          3、通过 ref 绑定要获取的元素或组件
        */}
        <h3>useRef</h3>
        <div ref={testRef}>通过 useRef 获取该 dom，并修改颜色</div>

        {/* 
          useContext()
          实现步骤：
          1、使用 createContext 创建 Context 对象：const Context = createContext()
          2、在顶层组件通过 Context.Provider 包裹根元素并提供数据
          3、在底层通过 useContext 函数获取数据：const 值名 = useContext(Context)

          注意：查找机制：useContext 优先从 Provider 上的 value 查找，如果查找不到，就会在 createContext() 的括号中取值
        */}
        <h3>useContext</h3>
        <CompA />
        <button onClick={() => setContextValue(contextValue + 1)}>点击context值加一</button>
      </div>
    </Context.Provider>
  )
}

export default App
